package org.infodavid.common.notifier.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.naming.Context;
import javax.naming.NamingException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;

import org.infodavid.common.annotations.AImplementation;
import org.infodavid.common.annotations.AImplementation.ERuntime;
import org.infodavid.common.messaging.MessagingService;
import org.infodavid.common.naming.NamingUtil;
import org.infodavid.common.notifier.Constants;
import org.infodavid.common.notifier.IAcknowledgment;
import org.infodavid.common.notifier.INotifierCommand;
import org.infodavid.common.notifier.INotifierRemoteBean;
import org.infodavid.common.notifier.NotifierHelper;
import org.infodavid.common.notifier.exceptions.NotifierException;
import org.infodavid.common.util.HelperUtil;
import org.infodavid.common.util.IVisitor;

/**
 * The Class NotifierHelperImpl.
 */
@AImplementation(runtime = ERuntime.DEFAULT, value = NotifierHelper.class)
public class NotifierHelperImpl extends NotifierHelper {

  /** The Constant serialVersionUID. */
  private static final long serialVersionUID = 8145421586918415739L;

  /** The Constant log. */
  private static final Log LOGGER = LogFactory.getLog(NotifierHelperImpl.class);

  /** The notifier address. */
  private String notifierAddress = null;

  /**
   * Instantiates a new notifier helper impl.
   */
  protected NotifierHelperImpl() {
    super();
  }

  /**
   * Gets the initial context according to the notifier remote or local host.
   * @return the initial context
   * @throws NamingException the naming exception
   */
  private Context getInitialContext() throws NamingException {
    String address = getNotifierAddress();

    if (address == null || address.trim().length() == 0) {
      address = System.getProperty(Constants.NOTIFIER_HOST_SYSTEM_PROPERTY);
    }

    if (address == null || address.trim().length() == 0) {
      address = Constants.DEFAULT_NOTIFIER_HOST;
    }

    return NamingUtil.getContext(address);
  }

  /**
   * Gets the client retry count.
   * @return the retry count
   */
  private int getRetryCount() {
    final String value = System.getProperty(Constants.CLIENT_RETRY_COUNT_SYSTEM_PROPERTY);
    int result = Constants.DEFAULT_CLIENT_RETRY_COUNT;

    if (StringUtils.isNotEmpty(value)) {
      try {
        result = Double.valueOf(value).intValue();
      }
      catch (final Exception e) {
        result = -1;
      }
    }

    if (result < 0) {
      result = Constants.DEFAULT_CLIENT_RETRY_COUNT;
    }

    return result;
  }

  /**
   * Gets the client retry period.
   * @return the retry period
   */
  private long getRetryPeriod() {
    final String value = System.getProperty(Constants.CLIENT_RETRY_PERIOD_SYSTEM_PROPERTY);
    long result = Constants.DEFAULT_CLIENT_RETRY_PERIOD;

    if (value == null || value.trim().length() == 0) {
      try {
        result = Double.valueOf(value).longValue();
      }
      catch (final Exception e) {
        result = -1;
      }
    }

    if (result < 0) {
      result = Constants.DEFAULT_CLIENT_RETRY_PERIOD;
    }

    return result;
  }

  /**
   * Execute.
   * @param <T> the type of the data
   * @param visitor the visitor
   * @param data the data
   * @throws Throwable the error
   */
  protected <I extends Object,O extends Object> void execute(final IVisitor<I,O> visitor,
      final I data) throws Throwable {
    HelperUtil.getInstance().execute(visitor, data, getRetryCount(), getRetryPeriod());
  }

  /**
   * Gets the default sender email.
   * @return the default sender email
   */
  private String getSenderEmail() {
    final String value = System.getProperty(Constants.SENDER_EMAIL_SYSTEM_PROPERTY);
    String result;

    if (value == null || value.trim().length() == 0) {
      result = Constants.DEFAULT_SENDER_EMAIL;
    }
    else {
      result = value;
    }

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.notifier.INotifier#send(org.infodavid.common.notifier.INotifierCommand[])
   */
  public IAcknowledgment[] send(final INotifierCommand... commands) throws NotifierException {
    return send(null, commands);
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.notifier.INotifier#send(org.joda.time.DateTime,
   * org.infodavid.common.notifier.INotifierCommand[])
   */
  public IAcknowledgment[] send(final DateTime date, final INotifierCommand... commands) {
    final boolean isDebugEnabled = LOGGER.isDebugEnabled();
    final IAcknowledgment[] results;

    if (commands == null || commands.length == 0) {
      if (isDebugEnabled) {
        LOGGER.debug("No notification to send");
      }

      results = new IAcknowledgment[0];
    }
    else {
      if (isDebugEnabled) {
        LOGGER.debug("Preparing notifications...");
      }

      final List<IAcknowledgment> acknowledgments = new ArrayList<IAcknowledgment>(commands.length);
      final MessagingService messagingService = MessagingService.getInstance();
      final long retryPeriod = getRetryPeriod();
      final int retryCount = getRetryCount();
      INotifierRemoteBean bean = null;
      Context context;
      IAcknowledgment[] acks;
      INotifierCommand cmd;
      int retries = 0;
      int i = 0;

      do {
        cmd = commands[i];
        acks = null;

        if (cmd == null) {
          if (isDebugEnabled) {
            LOGGER.debug("Notification command is null");
          }

          i++;
        }
        else {
          if (cmd.getFrom() == null || cmd.getFrom().trim().length() == 0) {
            cmd.setFrom(getSenderEmail());
          }

          try {
            if (date == null || cmd.isImmediate() || date.isBeforeNow()) {
              if (isDebugEnabled) {
                LOGGER.debug("Preparing notification for: " + date);
              }

              if (bean == null) {
                context = getInitialContext();
                bean = (INotifierRemoteBean)context.lookup(Constants.REMOTE_BEAN_JNDI_NAME);
              }

              acks = bean.send(date, cmd);
            }
            else {
              if (isDebugEnabled) {
                LOGGER.debug("Preparing notification in asynchronous mode: " + cmd);
              }

              messagingService.sendObjectMessages(Constants.NOTIFIER_QUEUE_JNDI_NAME, cmd);

              acks = new IAcknowledgment[] {
                new AcknowledgmentImpl(cmd)
              };
            }

            if (isDebugEnabled) {
              LOGGER.debug("Notification sent: " + Arrays.toString(acks));
            }

            i++;
          }
          catch (final Throwable e) {
            retries++;

            if (retries >= retryCount) {
              LOGGER.warn("An error occurs while preparing notification", e);

              acks = new IAcknowledgment[] {
                new AcknowledgmentImpl(cmd, e)
              };

              i++;
              retries = 0;
            }
            else {
              LOGGER.warn("An error occurs while preparing notification (retry=" + retries
                    + "), retrying after " + retryPeriod + " ms", e);

              try {
                Thread.sleep(retryPeriod);
              }
              catch (final InterruptedException e1) {
                LOGGER.warn("An error occurs while setting retry period", e1);
              }
            }
          }

          if (acks == null || acks.length == 0) {
            if (isDebugEnabled) {
              LOGGER.debug("Notification acknowledgment");
            }
          }
          else {
            acknowledgments.addAll(Arrays.asList(acks));
          }
        }
      }
      while (i < commands.length);

      results = acknowledgments.toArray(new IAcknowledgment[acknowledgments.size()]);
    }

    return results;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.notifier.NotifierHelper#setNotifierAddress(java.lang.String)
   */
  @Override
  public void setNotifierAddress(final String value) {
    notifierAddress = value;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.notifier.NotifierHelper#getNotifierAddress()
   */
  @Override
  public String getNotifierAddress() {
    return notifierAddress;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.notifier.NotifierHelper#throwExceptionIfError(org.infodavid.common.notifier
   * .IAcknowledgment[])
   */
  @Override
  public void throwExceptionIfError(final IAcknowledgment... acknowledgments)
      throws NotifierException {
    if (acknowledgments == null || acknowledgments.length == 0) {
      LOGGER.debug("No acknowledgments");
    }
    else {
      for (final IAcknowledgment ack : acknowledgments) {
        if (ack.getError() != null) {
          LOGGER.debug("Error found in notification acknowledgment: " + ack, ack.getError());

          throw new NotifierException(ack.getError());
        }
      }
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.notifier.NotifierHelper#warnIfError(org.infodavid.common.notifier.IAcknowledgment
   * [])
   */
  @Override
  public void warnIfError(final IAcknowledgment... acknowledgments) throws NotifierException {
    if (acknowledgments == null || acknowledgments.length == 0) {
      LOGGER.debug("No acknowledgments");
    }
    else {
      for (final IAcknowledgment ack : acknowledgments) {
        if (ack.getError() != null) {
          LOGGER.warn("Error found in notification acknowledgment: " + ack, ack.getError());
        }
      }
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.notifier.INotifier#cancel(java.lang.String[])
   */
  public void cancel(String... keys) throws NotifierException {
    final boolean isDebugEnabled = LOGGER.isDebugEnabled();

    if (keys == null || keys.length == 0) {
      if (isDebugEnabled) {
        LOGGER.debug("No notification to cancel");
      }
    }
    else {
      if (isDebugEnabled) {
        LOGGER.debug("Cancelling notifications...");
      }

      final long retryPeriod = getRetryPeriod();
      final int retryCount = getRetryCount();
      INotifierRemoteBean bean = null;
      Context context;
      int retries = 0;

      do {
        try {
          if (bean == null) {
            context = getInitialContext();
            bean = (INotifierRemoteBean)context.lookup(Constants.REMOTE_BEAN_JNDI_NAME);
          }

          bean.cancel(keys);
          retries = retryCount;
        }
        catch (final Throwable e) {
          retries++;

          if (retries >= retryCount) {
            LOGGER.warn("An error occurs while cancelling notifications", e);
          }
          else {
            if (isDebugEnabled) {
              LOGGER.debug("An error occurs while cancelling notifications (retry=" + retries
                  + "), retrying after " + retryPeriod + " ms", e);
            }

            try {
              Thread.sleep(retryPeriod);
            }
            catch (final InterruptedException e1) {
              LOGGER.warn("An error occurs while setting retry period", e1);
            }
          }
        }
      }
      while (retries < retryCount);
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.notifier.INotifier#listRunning(int, int)
   */
  public List<INotifierCommand> listRunning(int start, int size) throws NotifierException {
    final boolean isDebugEnabled = LOGGER.isDebugEnabled();
    List<INotifierCommand> results;

    if (start < 0 || size <= 0) {
      if (isDebugEnabled) {
        LOGGER.debug("Invalid parameters");
      }

      results = Collections.emptyList();
    }
    else {
      if (isDebugEnabled) {
        LOGGER.debug("Listing running notifications...");
      }

      final long retryPeriod = getRetryPeriod();
      final int retryCount = getRetryCount();
      INotifierRemoteBean bean = null;
      Context context;
      int retries = 0;

      do {
        try {
          if (bean == null) {
            context = getInitialContext();
            bean = (INotifierRemoteBean)context.lookup(Constants.REMOTE_BEAN_JNDI_NAME);
          }

          results = bean.listRunning(start, size);
          retries = retryCount;
        }
        catch (final Throwable e) {
          retries++;

          if (retries >= retryCount) {
            LOGGER.warn("An error occurs while listing notifications", e);

            results = Collections.emptyList();
          }
          else {
            LOGGER.warn("An error occurs while listing notifications (retry=" + retries
                + "), retrying after " + retryPeriod + " ms", e);

            results = null;

            try {
              Thread.sleep(retryPeriod);
            }
            catch (final InterruptedException e1) {
              LOGGER.warn("An error occurs while setting retry period", e1);
            }
          }
        }
      }
      while (retries < retryCount);
    }

    return results;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.notifier.INotifier#listScheduled(int, int)
   */
  public List<INotifierCommand> listScheduled(int start, int size) throws NotifierException {
    final boolean isDebugEnabled = LOGGER.isDebugEnabled();
    List<INotifierCommand> results;

    if (start < 0 || size <= 0) {
      if (isDebugEnabled) {
        LOGGER.debug("Invalid parameters");
      }

      results = Collections.emptyList();
    }
    else {
      if (isDebugEnabled) {
        LOGGER.debug("Listing scheduled notifications...");
      }

      final long retryPeriod = getRetryPeriod();
      final int retryCount = getRetryCount();
      INotifierRemoteBean bean = null;
      Context context;
      int retries = 0;

      do {
        try {
          if (bean == null) {
            context = getInitialContext();
            bean = (INotifierRemoteBean)context.lookup(Constants.REMOTE_BEAN_JNDI_NAME);
          }

          results = bean.listScheduled(start, size);
          retries = retryCount;
        }
        catch (final Throwable e) {
          retries++;

          if (retries >= retryCount) {
            LOGGER.warn("An error occurs while listing notifications", e);

            results = Collections.emptyList();
          }
          else {
            LOGGER.warn("An error occurs while listing notifications (retry=" + retries
                + "), retrying after " + retryPeriod + " ms", e);

            results = null;

            try {
              Thread.sleep(retryPeriod);
            }
            catch (final InterruptedException e1) {
              LOGGER.warn("An error occurs while setting retry period", e1);
            }
          }
        }
      }
      while (retries < retryCount);
    }

    return results;
  }
}
